package com.gitee.jmash.rbac.service;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.microprofile.jwt.Claims;
import org.eclipse.microprofile.jwt.JsonWebToken;
import com.gitee.jmash.common.cache.SerialCache;
import com.gitee.jmash.common.excel.ExcelExport;
import com.gitee.jmash.common.grpc.GrpcContext;
import com.gitee.jmash.common.security.JmashPrincipal;
import com.gitee.jmash.common.utils.FreeMarkerUtil;
import com.gitee.jmash.common.utils.UUIDUtil;
import com.gitee.jmash.core.jaxrs.ParamsValidationException;
import com.gitee.jmash.core.orm.DtoPage;
import com.gitee.jmash.core.orm.DtoTotal;
import com.gitee.jmash.core.orm.cdi.JpaTenantService;
import com.gitee.jmash.core.orm.jpa.TenantEntityManager;
import com.gitee.jmash.core.orm.tenant.TenantService;
import com.gitee.jmash.core.transaction.JakartaTransaction;
import com.gitee.jmash.core.utils.FilePathUtil;
import com.gitee.jmash.core.utils.NetSecretUtil;
import com.gitee.jmash.itext.PdfUtils;
import com.gitee.jmash.rbac.dao.DeptDao;
import com.gitee.jmash.rbac.dao.RoleDao;
import com.gitee.jmash.rbac.dao.TokenDao;
import com.gitee.jmash.rbac.dao.UserDao;
import com.gitee.jmash.rbac.dao.UserSecretDao;
import com.gitee.jmash.rbac.dao.UsersJobsDao;
import com.gitee.jmash.rbac.dao.UsersRolesDao;
import com.gitee.jmash.rbac.entity.DeptEntity;
import com.gitee.jmash.rbac.entity.RoleEntity;
import com.gitee.jmash.rbac.entity.TokenEntity;
import com.gitee.jmash.rbac.entity.TokenEntity.TokenPk;
import com.gitee.jmash.rbac.entity.UserEntity;
import com.gitee.jmash.rbac.entity.UserSecretEntity;
import com.gitee.jmash.rbac.entity.UsersJobsEntity;
import com.gitee.jmash.rbac.enums.SecretType;
import com.gitee.jmash.rbac.excel.UserHeaderExport;
import com.gitee.jmash.rbac.exception.JmashAuthenticationException;
import com.gitee.jmash.rbac.mapper.UserMapper;
import com.gitee.jmash.rbac.utils.PwdHashUtil;
import com.gitee.jmash.rbac.utils.RunAsUtil;
import jakarta.enterprise.inject.Typed;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import jakarta.validation.executable.ValidateOnExecution;
import jmash.protobuf.TableHead;
import jmash.protobuf.TenantReq;
import jmash.rbac.protobuf.JobInfo;
import jmash.rbac.protobuf.UserDeptJobInfoRes;
import jmash.rbac.protobuf.UserExportReq;
import jmash.rbac.protobuf.UserInfo;
import jmash.rbac.protobuf.UserInfoPage;
import jmash.rbac.protobuf.UserJobs;
import jmash.rbac.protobuf.UserKey;
import jmash.rbac.protobuf.UserModel;
import jmash.rbac.protobuf.UserReq;
import jmash.rbac.protobuf.UserStatus;
import jmash.rbac.protobuf.VerifyUserReq;

/**
 * 用户 rbac_user读服务.
 *
 * @author <a href="mailto:service@crenjoy.com">crenjoy</a>
 */
@Typed(UserRead.class)
@Transactional(TxType.SUPPORTS)
@JpaTenantService
@ValidateOnExecution
public class UserReadBean implements UserRead, JakartaTransaction {

  protected TenantEntityManager tem = new TenantEntityManager();

  protected UserDao userDao = new UserDao(this.tem);
  protected TokenDao tokenDao = new TokenDao(this.tem);
  protected UserSecretDao userSecretDao = new UserSecretDao(this.tem);
  protected UsersRolesDao userRolesDao = new UsersRolesDao(this.tem);
  protected UsersJobsDao userJobsDao = new UsersJobsDao(this.tem);
  protected DeptDao deptDao = new DeptDao(this.tem);
  protected RoleDao roleDao = new RoleDao(this.tem);

  /**
   * 缓存.
   */
  public static SerialCache getCache() {
    return CDI.current().select(SerialCache.class).get();
  }

  @Override
  @SuppressWarnings("unchecked")
  public <T extends TenantService> T setTenant(String tenant) {
    this.tem.setTenant(tenant);
    return (T) this;
  }

  @Override
  public EntityManager getEntityManager() {
    return this.tem.getEntityManager();
  }

  @PersistenceContext(unitName = "ReadRbac")
  public void setEntityManager(EntityManager entityManager) {
    this.tem.setEntityManager(entityManager, false);
  }

  @Override
  public void setTenantOnly(String tenant) {
    this.tem.setTenantOnly(tenant);
  }

  @Override
  public String getTenant() {
    return this.tem.getTenant();
  }

  @Override
  public UserEntity findById(UUID entityId) {
    return userDao.find(entityId);
  }

  @Override
  public UserModel findUserById(UUID userId) {
    UserEntity user = this.findById(userId);
    if (user == null) {
      throw new RuntimeException("未查询到该用户！");
    }
    UserModel.Builder userInfo = UserMapper.INSTANCE.model(user).toBuilder();
    // 角色.
    List<UUID> roleIds = userRolesDao.findUserRoles(userId);
    if (roleIds != null && roleIds.size() > 0) {
      for (UUID roleId : roleIds) {
        userInfo.addRoleIds(roleId.toString());
      }
    }
    List<UsersJobsEntity> usersJobsList = userJobsDao.findUserJobs(userId);
    // 用户部门岗位.
    if (usersJobsList != null && usersJobsList.size() > 0) {
      for (UsersJobsEntity entity : usersJobsList) {
        UserJobs.Builder userJobs = UserJobs.newBuilder();
        userJobs.setJobId(entity.getJobId().toString());
        userJobs.setDeptId(entity.getDeptId().toString());
        userInfo.addUserJobs(userJobs);
      }
    }
    // 标记运行用户.
    return RunAsUtil.markRunAsInfo(userInfo.build());
  }

  @Override
  public DtoPage<UserEntity, DtoTotal> findPageByReq(UserReq req) {
    return userDao.findPageByReq(req);
  }

  @Override
  public List<UserEntity> findListByReq(UserReq req) {
    return userDao.findListByReq(req);
  }

  @Override
  public UserEntity findByUserName(String directoryId, String userName) {
    return userDao.findByUserName(directoryId, userName);
  }

  @Override
  public boolean existUserName(VerifyUserReq req) {
    UUID userId = UUIDUtil.fromString(req.getUserId());
    UserEntity entity = userDao.findByUserName(req.getDirectoryId(), req.getUserName());
    if (entity != null && entity.getUserId().equals(userId)) {
      return false;
    }
    return entity != null;
  }

  @Override
  public List<String> findDirectoryIds() {
    return userDao.findDirectoryIds();
  }

  @Override
  public List<UserInfo> findUserListByReq(UserReq req) {
    List<UserEntity> list = userDao.findListByReq(req);
    if (list == null || list.size() == 0) {
      return Collections.emptyList();
    }
    List<UserInfo> result = buildUserInfo(req.getDeptId(), list);
    return result;
  }

  /**
   * 构造用户部门及岗位信息.
   */
  private List<UserInfo> buildUserInfo(String deptId, List<UserEntity> userList) {
    List<UserInfo> result = new ArrayList<>(userList.size());
    for (UserEntity entity : userList) {
      UserInfo.Builder builder = UserInfo.newBuilder();
      builder.setKey(entity.getUserId().toString());
      builder.setValue(entity.getRealName());
      builder.setMobilePhoneIns(entity.getMobilePhoneIns());
      if (StringUtils.isNotBlank(entity.getUnifiedId())) {
        builder.setUnifiedId(entity.getUnifiedId());
      }
      List<UsersJobsEntity> usersJobsList = new ArrayList<>();
      if (StringUtils.isNotBlank(deptId)) {
        usersJobsList = userJobsDao.findUserJobs(UUIDUtil.fromString(deptId), entity.getUserId());
      } else {
        usersJobsList = userJobsDao.findUserJobs(entity.getUserId());
      }
      if (usersJobsList == null || usersJobsList.size() == 0) {
        result.add(builder.build());
        continue;
      }
      UsersJobsEntity usersJob = usersJobsList.get(0);
      DeptEntity dept = deptDao.find(usersJob.getDeptId());
      if (dept != null) {
        builder.setDeptId(dept.getDeptId().toString());
        builder.setDeptName(dept.getDeptName());
      }
      RoleEntity role = roleDao.find(usersJob.getJobId());
      if (role != null) {
        builder.setJobId(role.getRoleId().toString());
        builder.setJobName(role.getRoleName());
      }
      result.add(builder.build());
    }
    return result;
  }

  @Override
  public UserInfoPage findUserPageByReq(UserReq req) {
    DtoPage<UserEntity, DtoTotal> page = userDao.findPageByReq(req);
    UserInfoPage modelPage = UserMapper.INSTANCE.pageUserInfo(page);
    List<UserInfo> resultsList = buildUserInfo(req.getDeptId(), page.getResults());
    return modelPage.toBuilder().clearResults().addAllResults(resultsList).build();
  }

  @Override
  public UserEntity loginByUserName(String directoryId, String userName, String encodePwd) {
    UserEntity entity = userDao.findByUserName(directoryId, userName);
    // 找不到用户
    if (entity == null) {
      throw new JmashAuthenticationException(0, "用户名或者密码错误");
    }
    // 用户登录常规检查.
    checkUserLogin(entity);
    if (StringUtils.equals(entity.getEmail(), userName) && !entity.getEmailApproved()) {
      throw new JmashAuthenticationException(entity.getUserId(), 4, "该账号邮箱未审核,暂不能邮箱登录");
    }
    if (StringUtils.equals(entity.getMobilePhone(), userName) && !entity.getPhoneApproved()) {
      throw new JmashAuthenticationException(entity.getUserId(), 5, "该账号手机号未审核,暂不能邮箱登录");
    }
    UserSecretEntity secret = userSecretDao.findByKey(entity.getUserId(), SecretType.Login.name());
    if (secret == null) {
      throw new JmashAuthenticationException(entity.getUserId(), 7, "账号未配置登录密码,可通过重置密码功能配置.");
    }
    // 密钥/临时密钥
    String pwd = NetSecretUtil.decrypt(encodePwd);
    String pwdValue = PwdHashUtil.encrypt(secret.getPwdFormat(), pwd, secret.getPwdAlt());
    if (StringUtils.equals(secret.getPwdValue(), pwdValue)) {
      return entity;
    } else {
      throw new JmashAuthenticationException(entity.getUserId(), 6, "用户名或者密码错误");
    }
  }

  // 用户登录常规检查.
  protected void checkUserLogin(UserEntity entity) {
    if (null == entity) {
      throw new RuntimeException("用户未找到");
    }
    // 审核没有通过
    if (!entity.getApproved()) {
      throw new JmashAuthenticationException(entity.getUserId(), 1, "该账号未审核,暂不能登录");
    }
    // 用户被锁
    if (UserStatus.locked.equals(entity.getStatus())) {
      throw new JmashAuthenticationException(entity.getUserId(), 2, "该账号已锁,密码错误超过5次，请2小时后再试");
    }
    // 用户被禁用
    if (UserStatus.disabled.equals(entity.getStatus())) {
      throw new JmashAuthenticationException(entity.getUserId(), 3, "该账号被禁用");
    }
    // 用户已移除
    if (entity.getDeleted()) {
      throw new JmashAuthenticationException(entity.getUserId(), 4, "该账号已移除");
    }
  }

  @Override
  public TokenEntity findTokenById(TokenPk entityId) {
    return tokenDao.find(entityId);
  }
  
  @Override
  public TokenEntity findTokenByAuthzCode(String clientId, String authorizationCode) {
    return tokenDao.findByAuthzCode(clientId, authorizationCode);
  }

  @Override
  public TokenEntity findTokenByRefreshToken(String clientId, String refreshToken) {
    return tokenDao.findByRefreshToken(clientId, refreshToken);
  }

  @Override
  public TokenEntity findTokenByAccessToken(String clientId, String accessToken) {
    return tokenDao.findByAccessToken(clientId, accessToken);
  }

  @Override
  public TokenEntity findTokenByWebToken(JsonWebToken webToken) {
    UUID userId = UUIDUtil.fromString(webToken.getName());
    String clientId = webToken.getClaim(Claims.azp.name());
    return tokenDao.find(new TokenPk(userId, clientId));
  }

  @Override
  public String downloadUserTemplate(TenantReq request) throws FileNotFoundException, IOException {
    return exportUser(true, Collections.emptyList(), UserExportReq.getDefaultInstance());
  }

  @Override
  public String exportUser(UserExportReq request) throws FileNotFoundException, IOException {
    List<UserEntity> list = userDao.findListByReq(request.getReq());
    return exportUser(false, list, request);
  }

  /**
   * 导出信息
   *
   * @param isTemplate 是否导入模板
   * @param userList   导出数据
   * @param req        导出请求
   * @return 导出文件路径
   */
  private String exportUser(boolean isTemplate, List<UserEntity> userList, UserExportReq req)
      throws FileNotFoundException, IOException {
    ExcelExport excelExport = new ExcelExport();
    // 页签
    excelExport.createSheet(UserHeaderExport.TITLE);
    // 增加表头
    UserHeaderExport.addHeaderExport(isTemplate, excelExport, req.getTableHeadsList());
    // 写标题
    String title = StringUtils.isBlank(req.getTitle()) ? UserHeaderExport.TITLE : req.getTitle();
    excelExport.writeTitle(title);
    // 写表头
    excelExport.writeHeader();
    // 写数据
    excelExport.writeListData(userList);
    // 增加数据校验
    excelExport.dictDataValidation(userList.size(), 3000);
    // 写入文件
    String fileName = StringUtils.isBlank(req.getFileName()) ? title : req.getFileName();
    String fileSrc = FilePathUtil.createNewTempFile(fileName + ".xlsx", "excel");
    String realFileSrc = FilePathUtil.realPath(fileSrc, true);
    excelExport.getWb().write(new FileOutputStream(realFileSrc));
    return realFileSrc;
  }

  @Override
  public String printUser(UserExportReq req) throws FileNotFoundException, IOException {
    List<TableHead> tableHeads = req.getTableHeadsCount() > 0 ? req.getTableHeadsList()
        : UserHeaderExport.getTableHeadList();
    List<UserEntity> userList = userDao.findListByReq(req.getReq());
    // 数据Map.
    Map<String, Object> dataMap = new HashMap<String, Object>();
    dataMap.put("tableHeads", tableHeads);
    dataMap.put("userList", userList);
    String title = StringUtils.isBlank(req.getTitle()) ? UserHeaderExport.TITLE : req.getTitle();
    dataMap.put("title", title);
    dataMap.put("approvedMap", UserHeaderExport.getApprovedMap());
    dataMap.put("genderMap", UserHeaderExport.getGenderMap());
    dataMap.put("statusMap", UserHeaderExport.getUserStatusMap());

    // 生成HTML.
    String htmlStr = FreeMarkerUtil.parse(UserReadBean.class, dataMap, "/print/rbac_user.ftl");
    // 文件路径
    String fileName = StringUtils.isBlank(req.getFileName()) ? title : req.getFileName();
    String fileSrc = FilePathUtil.createNewTempFile(fileName + ".pdf", "pdf");
    String realFileDir = FilePathUtil.realPath(fileSrc, true);
    // 登录用户
    JmashPrincipal principal = GrpcContext.getPrincipal();
    String realname = (principal != null) ? principal.getSubject() : "未登录";
    // PDF生成
    try (OutputStream outS = new FileOutputStream(realFileDir)) {
      PdfUtils.convertToPdf(htmlStr, outS, "", realname, true);
    }
    return realFileDir;
  }

  @Override
  public void close() throws Exception {
    CDI.current().destroy(this);
  }

  @Override
  public UserDeptJobInfoRes getUserDeptInfo(UserKey req) {
    UUID userId = UUIDUtil.fromString(req.getUserId());
    UserEntity userEntity = userDao.find(userId);
    if (null == userEntity) {
      throw new ParamsValidationException("userId", "Not Fund " + userId);
    }
    UserDeptJobInfoRes userDeptInfo = UserMapper.INSTANCE.modelUserDept(userEntity);

    List<UsersJobsEntity> userJobs = userJobsDao.findUserJobs(userId);
    if (!userJobs.isEmpty()) {
      List<JobInfo> jobInfoList = new ArrayList<>();
      userJobs.forEach(item -> {
        UUID deptId = item.getDeptId();
        UUID jobId = item.getJobId();
        DeptEntity deptEntity = deptDao.find(deptId);
        RoleEntity jobEntity = roleDao.find(jobId);
        JobInfo jobInfo =
            JobInfo.newBuilder().setJobId(jobId.toString()).setDeptId(deptId.toString()).build();
        if (deptEntity != null) {
          jobInfo = jobInfo.toBuilder().setDeptName(deptEntity.getDeptName()).build();
        }
        if (jobEntity != null) {
          jobInfo = jobInfo.toBuilder().setJobName(jobEntity.getRoleName()).build();
        }
        jobInfoList.add(jobInfo);
      });
      return userDeptInfo.toBuilder().addAllJobInfo(jobInfoList).build();
    }
    return userDeptInfo;
  }
}
